package com.gray.quartz.wheel;

import com.gray.quartz.simple.SimpleQuartz;
import org.quartz.JobKey;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;

/**
 * Created on 2021/6/5.
 *
 * @author grayCat
 * @since 1.0
 */
@Component
public class WheelManager {

    //最小单位
    private static final int TIMER_TICK = 64;
    //时间轮数量
    private static final int TIMER_LEVEL = 26;

    private HashMap<Long, WheelTask>[] timerPool;

    //开启一个定时器，间隔时间执行
    private long lastTick = System.currentTimeMillis() / TIMER_TICK;

    private ConcurrentLinkedQueue<WheelTask> inQueue = new ConcurrentLinkedQueue();

    private JobKey jobKey;

    private boolean open;

    @Autowired
    SimpleQuartz simpleQuartz;

    private long newId = 0;

    public WheelManager(){
        timerPool = new HashMap[TIMER_LEVEL];
        for (int i = 0; i < TIMER_LEVEL; i ++) {
            timerPool[i] = new HashMap<>();
        }
    }

    public long add(Runnable runnable, long delay){
        newId += 1;
        if(newId <= 0){
            newId = 1;
        }

        WheelTask task = new WheelTask();
        task.endTime = System.currentTimeMillis() + delay;
        task.id = newId;
        task.rb = runnable;

        inQueue.add(task);
        return newId;
    }

    public void update(long id, long delay){
        WheelTask task = new WheelTask();
        task.endTime = System.currentTimeMillis() + delay;
        task.id = id;
        inQueue.add(task);
    }

    public void remove(long id){
        WheelTask task = new WheelTask();
        task.id = id;
        inQueue.add(task);
    }

    public void changeState(boolean open){
        if(this.open != open){
            this.open = open;
        }else {
            return;
        }

        if(this.open){
            autoRun();
        }else {
            try {
                simpleQuartz.shutdown(jobKey);
            } catch (SchedulerException e) {
                e.printStackTrace();
            }
        }
    }

    private void autoRun(){
        try {
            jobKey = simpleQuartz.delayTimmer(TIMER_TICK, TimeUnit.MILLISECONDS, this::run);
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }

    private WheelTask del(long id){
        for (int i = TIMER_TICK -1; i >= 0; i --){
            HashMap<Long, WheelTask> pool = timerPool[i];
            WheelTask task = pool.get(id);
            if (task != null) {
                pool.remove(id);
                return task;
            }
        }

        return null;
    }

    private void modify(WheelTask task){
        if(task.id == 0){
            return;
        }

        if(task.rb != null && task.endTime > 0){
            add(task);
            return;
        }

        if(task.rb == null && task.endTime > 0){
            //更新
            WheelTask old = del(task.id);
            if(old != null){
                old.endTime = task.endTime;
                add(old);
            }
        }else if(task.endTime == 0){
            del(task.id);
        }
    }

    private void add(WheelTask wheelTask){
        long diff = wheelTask.endTime - System.currentTimeMillis();
        if (diff <= 0){
            wheelTask.rb.run(); //这里后面使用线程池
            return;
        }

        if (diff < TIMER_TICK){
            diff = TIMER_TICK;
        }

        for (int i = TIMER_LEVEL - 1;i >=0;i--){
            if (diff >= ((long) TIMER_TICK << i)){
                timerPool[i].put(wheelTask.id, wheelTask);
                break;
            }
        }
    }

    private void run(){
        try {
            WheelTask wheelTask = inQueue.poll();
            do {
                if(wheelTask == null){
                    break;
                }

                modify(wheelTask);
                wheelTask = inQueue.poll();
            }while (wheelTask != null);

            lastTick = doTick(lastTick);
            autoRun();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private long doTick(long lastTick){
        long newTick = System.currentTimeMillis() / TIMER_TICK;
        if (newTick < lastTick){
            return newTick;
        }

        do {
            lastTick++;
            for (int i = TIMER_LEVEL - 1; i >= 0; i--) {
                long mask = 1L << i - 1;
                if ((lastTick & mask) == 0) {
                    trigger(i);
                }
            }

        } while (lastTick < newTick);
        return newTick;
    }

    private void trigger(int level){
        long newTick = System.currentTimeMillis();
        HashMap<Long, WheelTask> lkotMap = timerPool[level];

        List<Long> list = new LinkedList<>();

        try {
            for (WheelTask task : lkotMap.values()) {
                if (task.endTime - newTick < (1L << level) * TIMER_LEVEL){
                    if(level != 0){
                        timerPool[level - 1].put(task.id, task);
                        list.add(task.id);
                    }else if (newTick > task.endTime){
                        task.rb.run(); //后面使用线程池
                        list.add(task.id);
                    }
                }
            }
            for (long key : list) {
                lkotMap.remove(key);
            }
        }catch (Exception e){
            e.printStackTrace();
        }

    }
}
